<template>
    <canvas ref="canvas" width="100" height="100" class="p-painter-vehicles"></canvas>
</template>

<script>
import { mapState } from 'vuex';
import { VEHICLE_TYPES } from '../core/SYSTEM.enum.js'; //eslint-disable-line
import bus from '../utils/Eventbus.js';
import BlobHelper from '../utils/v-blob.js';
import Point from '../utils/Point.js';
import SVG from '../core/SVG.js';
import * as enums from '../store/status-enums.js';
import * as Matrix from '../utils/Matrix.js';
import * as Painter from '../core/Painter.js';
import * as Chef from '../core/Chef.js';
import * as Graph from '../utils/Graph/Graph.js';
import _ from 'lodash';
import i18n from '../lang/index.js';
const $t = i18n.global.t;
// import * as API from '../js/API.js';

/**
 * p-painter-vehicles
 * @author wangdasong
 * @date 2023.01.30
 */
export default {
    name: 'p-painter-vehicles',
    components: {},
    props: {
        // display mode of the map, can be '2d' or 'isometric', default is '', means '2d', maybe '3d' in the future.
        mode: {
            type: String,
            default: ''
        },
        map: Object, // map arguments
        offline: Boolean,
        displayVehicleLabels: Boolean,
        scalingable: {
            type: Boolean,
            default: true
        },
        draggable: {
            type: Boolean,
            default: true
        },
        hideVehicles: {
            type: Boolean,
            default: false
        }
    },
    data() {
        return {
            trueName: this.$options.name,
            matrix: null,
            ctx: null,
            ratio: null,
            dimensions: null, // [-10000, 10000, -10000, 10000],
            refreshing: false,
            startTunedPos: null
        };
    },
    computed: {
        ...mapState({
            isShowEnvelopeActive: state => state.businessStatus.isShowEnvelopeActive,
            selectedVehicles: state => state.businessStatus.selectedVehicles,
            isMultiSelectActive: state => state.businessStatus.isMultiSelectActive,
            currentJobs: state => state.businessStatus.selectedJobs,
            openedBoxes: state => state.businessStatus.openedBoxes,
            isDrawPathsActive: state => state.businessStatus.isDrawPathsActive,
            isTrackActive: state => state.businessStatus.isTrackActive,
            isAltFnActive: state => state.businessStatus.isAltFnActive,
            isAllData: state => state.businessStatus.isAllData
        }),
        isometric() {
            return this.mode === 'isometric';
        }
    },
    watch: {
        hideVehicles(val) {
            console.log('p-painter-vehicles.watch.hideVehicles:', val);
        }
    },
    created() {
        bus.on(`${this.trueName}.refresh`, this.refresh);
        bus.on(`${this.trueName}.editChanged`, this.editChanged);

        // bus.on(`${this.trueName}.repaint`, this.repaint);
        bus.on(`${this.trueName}.locateVehicle`, this.locateVehicle);
    },
    beforeDestroy: function() {
        if (this.timer) clearTimeout(this.timer);
        // 清除事件监听
        bus.off(`${this.trueName}.refresh`, this.refresh);

        // bus.off(`${this.trueName}.repaint`, this.repaint);
        bus.off(`${this.trueName}.locateVehicle`, this.locateVehicle);
        bus.off(`${this.trueName}.editChanged`, this.editChanged);
    },
    mounted() {
        this.init();
    },
    methods: {
        init() {
            this.trackerInit();
            console.log('p-painter-vehicles.init: ', this._uid, this.dimensions);
            if (!this.maxTry) this.maxTry = 1;
            if ((!this.$root.resources || !this.$root.resources.paths) && this.maxTry < 100) {
                this.maxTry++;
                return (this.timer = setTimeout(this.init.bind(this), 200));
            } else {
                this.maxTry = 0;
                this.initCanvas();
                // this.initModelsCache();
                // calculate models img to cache, just for offscreen render. 
                this.calculateModelCache();
            }
            // this.initCanvas();
            // this.initModelsCache();
        },
        initCanvas() {
            let obj = this.$refs.canvas;
            if (!this.ctx) this.ctx = obj.getContext('2d');
            if (!this.ratio) this.ratio = Matrix.getPixelRatio(this.ctx);
            let w = parseInt(this.$parent.$el.clientWidth, 10) * this.ratio,
                h = parseInt(this.$parent.$el.clientHeight, 10) * this.ratio;
            if (obj.width === w && obj.height === h) return;
            obj.setAttribute('width', w);
            obj.setAttribute('height', h);
        },
        initCanvasCtx(ctx = this.ctx) {
            // Isometric logic. 
            let cp = new Point(this.map.originX, this.map.originY);
            // if (this.isometric) cp = Point.getIsometricPos(cp.x, cp.y);
            ctx.translate(cp.x, cp.y);
            ctx.scale(this.map.scaling, this.map.scaling);
            ctx.rotate(this.map.rotation * (Math.PI / 180));
            // console.log('p-painter-paths.initCanvasCtx: ', cp);
        },
        // 初始化车辆模型图片缓存
        initModelsCache() {
            if (!this.$root.resources || !this.$root.resources.models || !this.$root.resources.models.length) return;
            this.$root.resources.models.forEach(m => {
                // let svg = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter id="shadowFilter" x="-25%" y="-25%" width="150%" height="150%"><feGaussianBlur in="SourceAlpha" stdDeviation="45"></feGaussianBlur><feOffset result="offsetBlur"></feOffset><feFlood flood-color="rgba(0,0,0,0.8)"></feFlood><feComposite in2="offsetBlur" operator="in"></feComposite><feMerge><feMergeNode></feMergeNode><feMergeNode in="SourceGraphic"></feMergeNode></feMerge></filter></defs>
                // <g transform="matrix(1 0 0 1 ${m.size.x / 2} ${m.size.y / 2})" filter="shadowFilter">
                // <g xmlns="http://www.w3.org/2000/svg">${m.shape}</g></g></svg>`;
                let svg = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="matrix(1 0 0 1 ${m
                    .size.x / 2} ${m.size.y / 2})"><g xmlns="http://www.w3.org/2000/svg">${m.shape}</g></g></svg>`;
                // console.warn('p-painter-vehicles.initModelsCache: ', m.vehicleKey, svg);
                BlobHelper.setImgCacheBySvg(m.id, {
                    width: m.size.x,
                    height: m.size.y,
                    svg: svg
                });
            });
        },

        repaint(recalculate = false) {
            console.log('p-painter-vehicles.repaint: ');
            if (recalculate) {
                //
            } else {
                this.dimensions = null;
            }
            this.init();
        },

        // tracker
        trackerInit() {
            this.trackRoutes = []; // use to draw paths when in a new map...
            this.trackRoutesLimit = 100000;
            this.trackLast = Date.now();
            this.trackInterval = 100;
        },
        trackerUpdate(vehicleId, pos, offsetProgress) {
            if (
                !this.selectedVehicles ||
                this.selectedVehicles.length > 1 ||
                this.selectedVehicles.indexOf(vehicleId) === -1
            )
                return;
            this.trackerStroke(offsetProgress);
            if (Date.now() - this.trackLast < this.trackInterval) return;
            if (this.trackRoutes.length <= this.trackRoutesLimit) this.trackRoutes.push(pos);
            else {
                this.trackRoutes.shift();
                this.trackRoutes.push(pos);
            }
            this.trackLast = Date.now();
        },
        trackerStroke(offsetProgress) {
            // console.warn('tracker: ', this.trackRoutes);
            let w = (1 / this.map.scaling) * 10,
                ox = -w / 2,
                oy = -w / 2;
            this.ctx.save();
            this.ctx.beginPath();
            this.ctx.fillStyle = '#F75C2F';
            this.trackRoutes.forEach(p => {
                // let h = -(p.h * Math.PI) / 180,
                let op = Point.getProgressPoint(p, offsetProgress);
                // console.log(p.h, h, px, py);
                this.ctx.fillRect(p.x + ox + op.x, -p.y + oy + op.y, w, w);
            });
            this.ctx.closePath();
            // this.ctx.fillStyle = 'rgba(55, 55, 55, 1)';
            // this.ctx.fill();
            this.ctx.restore();
        },
        isHideVehicleMode() {
            /**
             * 有任务ID并且任务Id不能为-1 && pathsTable && !vehiclesTable && editStatus
             * 才能隐藏车辆模型
             * */
            let pathsTable = this.openedBoxes.includes('pathsTable'),
                vehiclesTable = this.openedBoxes.includes('vehiclesTable'),
                currentJobs = this.currentJobs && this.currentJobs.length === 1 && this.currentJobs[0] !== '-1';
            // console.error(pathsTable, vehiclesTable, currentJobs, this.editStatus);
            return currentJobs && pathsTable && !vehiclesTable && this.editStatus;
        },
        refresh() {
            // console.error();
            if (this.refreshing || !this.ctx) return;
            if (!this.$root.resources.vehicles || !this.$root.resources.vehicles.length) return;
            // if (this.hideVehicles) {
            //     this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            //     return;
            // }
            this.refreshing = true;
            this.initCanvas();
            // // calculate models img to cache, just for offscreen render.
            // this.calculateModelCache();

            // console.log('p-painter-vehicles.refresh: ');
            this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            this.ctx.save();
            let cp = new Point(this.map.originX, this.map.originY);
            if (this.isometric) cp = Point.getIsometricPos(cp.x, cp.y);
            this.ctx.translate(cp.x, cp.y);
            this.ctx.scale(this.map.scaling, this.map.scaling);
            this.ctx.rotate(this.map.rotation * (Math.PI / 180));
            if (this.isDrawPathsActive || this.isHideVehicleMode()) {
                Painter.drawVehicleRect(this.ctx, this.$root.resources.vehicles, this.map.scaling);
                // this.ctx.beginPath();
                // // this.$root.resources.vehicles.forEach(o => {
                // for (let i = 0, len = this.$root.resources.vehicles.length; i < len; i++) {
                //     let o = this.$root.resources.vehicles[i];
                //     if (!o.tunedPos) continue;
                //     let w = o.modelExt.size.x,
                //         h = o.modelExt.size.y,
                //         x = o.tunedPos.x - w / 2,
                //         y = -o.tunedPos.y - h / 2;
                //     // Painter.drawRoundRect(this.ctx, x, y, w, h, 3);
                //     // this.ctx.beginPath();
                //     if (o.tunedPos) {
                //         let rectCenterPoint = {
                //             x: x + w / 2,
                //             y: y + h / 2
                //         };
                //         if (!o.tunedPos.h) o.tunedPos.h = 0;
                //         this.ctx.translate(rectCenterPoint.x, rectCenterPoint.y);
                //         this.ctx.rotate(-(o.tunedPos.h * Math.PI) / 180);
                //         this.ctx.translate(-rectCenterPoint.x, -rectCenterPoint.y);
                //         this.ctx.rect(x, y, w, h);
                //         this.ctx.strokeStyle = o.statusExt.color;
                //         // this.ctx.lineWidth = o.outlineStrokeWidthExt;
                //         this.ctx.lineWidth = (1 / this.map.scaling) * 5;
                //         this.ctx.stroke();
                //         this.ctx.beginPath();
                //         this.ctx.translate(rectCenterPoint.x, rectCenterPoint.y);
                //         this.ctx.rotate((o.tunedPos.h * Math.PI) / 180);
                //         this.ctx.translate(-rectCenterPoint.x, -rectCenterPoint.y);
                //     }
                //     // this.ctx.closePath();
                // }
                // // this.ctx.closePath();
                // // this.ctx.stroke();
                // this.ctx.restore();
                this.refreshing = false;
                return;
            }
            this.$root.resources.vehicles.forEach(o => {
                if (!o.modelExt || !o.tunedPos) return;
                if (this.isTrackActive) this.trackerUpdate(o.id, o.tunedPos, o.modelExt.offsetProgress);
                this.drawBlinker(o);
                this.drawEnvelope(o);
                let hasPaint = false;
                let pos = o.tunedPos;
                if (o.id.indexOf('TCVSMR') !== -1 || o.id.indexOf('TCVSMF') !== -1) o.label = o.id.slice(6, 8);

                let ck = o.modelExt.id;
                if (o.label) ck = o.modelExt.id + '_' + o.label;

                let img = this.getModelCache(ck);
                if (!img) {
                    let label = o.label,
                        labelSize = o.modelExt.size.x / 3,
                        labelX = 0,
                        labelY = o.modelExt.size.x / 3.5;
                    if (o.modelExt.type === VEHICLE_TYPES.FOLLOWER) {
                        labelSize = o.modelExt.size.x / 2;
                        labelY = o.modelExt.size.x / 6;
                    }
                    let os = this.getGraphsByShapes(o.modelExt.shape);
                    this.setModelCtxCacheByGraphs(ck, o.modelExt, os, label, labelSize, labelX, labelY);
                    this.ctx.restore();
                    this.refreshing = false;
                    // console.log('-----------> ', ck, label);
                    return;
                }

                if (!this.isAllData) return;
                hasPaint = Painter.drawVehicle(this.ctx, img, o.modelExt, pos, this.map.scaling);

                if (hasPaint) this.drawSelected(o);
                this.drawJobWindow(o);
                // if (this.isTrackActive) this.trackerUpdate(o.id, o.tunedPos, o.modelExt.offsetProgress);
            });
            if (this.shadow) this.drawShadow();
            this.ctx.restore();
            this.refreshing = false;
        },
        drawBlinker(o) {
            if (!o.blinkerFillExt) return;
            let min = Math.min(o.modelExt.size.x, o.modelExt.size.y) / 2,
                max = Math.max(o.modelExt.size.x, o.modelExt.size.y) / 2 + o.blinkerRExt / 2,
                step = (max - min) / 50;
            if (!this.blinkerRExt) this.blinkerRExt = {};
            if (!this.step) this.step = {};
            if (!this.step[o.id]) this.step[o.id] = step;
            if (!this.blinkerRExt[o.id] || this.blinkerRExt[o.id] < min) {
                this.blinkerRExt[o.id] = min;
                this.step[o.id] = step;
            }
            if (this.blinkerRExt[o.id] > max) {
                this.blinkerRExt[o.id] = max;
                this.step[o.id] = -step;
            }
            this.blinkerRExt[o.id] += this.step[o.id];
            this.ctx.beginPath();
            this.ctx.arc(o.tunedPos.x, -o.tunedPos.y, this.blinkerRExt[o.id], 0, Math.PI * 2);
            this.ctx.closePath();
            this.ctx.fillStyle = o.blinkerFillExt;
            this.ctx.fill();
        },
        drawEnvelope(o) {
            if (!this.isShowEnvelopeActive) return;
            if (!o || !o.envelope || !o.envelope.length) return;
            this.ctx.save();
            Painter.drawEnvelope(this.ctx, o.envelope);
            this.ctx.restore();
        },
        drawSelected(o) {
            // console.log('p-painter-vehicles.refresh: ', o.id, this.selectedVehicles.indexOf(o.id));
            if (!this.selectedVehicles || !this.selectedVehicles.length || this.selectedVehicles.indexOf(o.id) < 0)
                return;
            this.ctx.save();
            Painter.drawVehicleSelected(this.ctx, o.modelExt, o.tunedPos, o.outlineStrokeWidthExt, this.map.scaling);
            this.ctx.restore();
        },
        drawJobWindow(o) {
            if (!o || !this.displayVehicleLabels) return;
            // if (!this.currentJobs || !this.currentJobs.length) return;
            let job = null;
            if (this.currentJobs && this.currentJobs.length && this.$root.resources.jobs) {
                let jobs = this.$root.resources.jobs.filter(j => this.currentJobs.indexOf(j.id) >= 0);
                job = jobs.find(v => v.vehicleKey === o.id);
                // if (job) jobProgress = Chef.getJobProgressById(job.id, this.$root.resources.jobs);
                // jobProgress = job.progressExt.value;
            }
            let labels = [];
            labels.push($t('lang.name') + ': ' + o.id);
            if (job)
                labels.push({
                    value: $t('lang.jobs') + ': ' + job.progressLabelExt + '%',
                    color: job.progressColorExt
                });
            // if (o.id === 'TCVSMR02') console.error(o);
            if (o.destinationExt && !!o.destinationExt.value)
                labels.push($t('lang.destination') + ': ' + o.destinationExt.value);
            if (o.locationExt && !!o.locationExt.value)
                labels.push($t('lang.path') + ': ' + o.locationExt.value);
            labels.push($t('lang.response') + ': ' + o.responseExt);
            let energy = { value: '--', color: 'rgba(10, 70, 30, 0.85)' };
            if (o.statusExt && o.statusExt['Energy [%]']) energy = o.statusExt['Energy [%]'];
            if (o.statusExt && o.statusExt['Batterie']) energy = o.statusExt['Batterie'];
            if (energy)
                labels.push({ value: $t('lang.energy') + ': ' + energy.value + ' %', color: energy.color });

            let model = o.modelExt,
                h = 200 * labels.length + 20,
                x = o.tunedPos.x + model.size.x,
                y = -(o.tunedPos.y + model.size.y);
            Painter.drawJobWindow(this.ctx, o.tunedPos, model, x, y, h, labels);
        },
        drawShadow() {
            if (!this.shadow) return;
            // Painter.drawShadow(this.ctx, this.shadow.modelExt, this.shadow.tunedPos, this.map.scaling);
            // let graphs = this.getModelCache(this.shadow.modelExt.id);
            // Painter.drawShadowByGraphs(this.ctx, graphs, this.shadow.modelExt, this.shadow.tunedPos, this.map.scaling);
            let img = this.getModelCache(this.shadow.modelExt.id);
            Painter.drawShadow(this.ctx, img, this.shadow.modelExt, this.shadow.tunedPos, this.map.scaling);
        },
        locateVehicle(id) {
            console.log('p-painter-vehicles.locateVehicle: ', id);
            if (!id) return;
            // let m = this.$root.resources.models.find(tm => id.startsWith(tm.vehicleKey)),
            let m = this.$root.resources.models.find(tm => {
                    if (id && id.startsWith && typeof id.startsWith === 'function') return id.startsWith(tm.vehicleKey);
                }),
                o = this.$root.resources.vehicles.find(v => v.id === id);
            if (!m || !o || !o.tunedPos) return;
            let d = Matrix.getVehicleDimensions(m, [o], null, this.ratio),
                zoomed = [d[0] - 5000, d[1] + 5000, d[2] - 5000, d[3] + 5000];
            console.log('p-painter-vehicles.locateVehicle: ', id, o, zoomed);
            this.$emit('dimensionsChange', zoomed);
        },
        // pointer events
        pointerDownFunc(cursorPos, transPos) {
            this.startPoint = transPos;
            this.touching = true;
            this.shadow = Chef.getVehicleByPosition(transPos, this.$root.resources.vehicles);
            if (this.shadow && this.shadow.properties && this.shadow.properties.pathId)
                this.startPath = this.shadow.properties.pathId;
            if (this.shadow) this.$emit('draggableChange', false);
            // console.log('p-painter-vehicles.pointerDownFunc: ', t);
        },
        pointerMoveFunc(cursorPos, transPos) {
            if (this.isMultiSelectActive) return;
            if (!this.touching || !this.shadow) return;
            this.shadow.tunedPos.x = transPos.x;
            this.shadow.tunedPos.y = transPos.y;
            let path = Chef.getPathByPoint(transPos, this.$root.resources.paths) || this.isPointInStroke(cursorPos);
            if (path && path.id) this.$store.commit(enums.DESELECT_PATHS_ALL, [path.id]);
            else this.$store.commit(enums.DESELECT_PATHS_ALL);
            this.shadowDestination = path;
            if (this.shadowDestination) {
                let lastPoint = this.shadowDestination.controlPoints[this.shadowDestination.controlPoints.length - 1];
                this.shadow.tunedPos.x = lastPoint.x;
                this.shadow.tunedPos.y = lastPoint.y;
                this.shadow.tunedPos.h = lastPoint.h;
            }
            // console.log('p-painter-vehicles.pointerMoveFunc: shadowDestination: ', this.shadowDestination);
        },
        isPointInStroke(cusPos) {
            let rtn = null;
            if (!cusPos) return;

            this.ctx.save();
            this.initCanvasCtx();
            let paths = _.cloneDeep(this.$root.resources.paths);
            /*
             * Q: controlPoints of undefined
             * A: paths is undefined
             */
            if (!paths || !paths.length) return;

            this.lastSelectedIdx = paths.length - 1;
            for (let i = this.lastSelectedIdx; i >= 0; i--) {
                /*
                 * Q: controlPoints of undefined
                 * A: if controlPoints is undefined or [], Don't draw path in painter
                 */
                if (paths[i] && paths[i].controlPoints && paths[i].controlPoints.length)
                    Painter.drawPath(this.ctx, paths[i].controlPoints, paths[i].width, paths[i].color);
                if (cusPos && this.ctx.isPointInStroke(cusPos.x, cusPos.y)) {
                    this.lastSelectedIdx = i - 1;
                    rtn = paths[i];
                    break;
                }
            }
            this.ctx.restore();
            if (!rtn) this.lastSelectedIdx = null;
            // console.error(rtn);
            return rtn;
        },
        pointerUpFunc(cursorPos, transPos) {
            if (this.isMultiSelectActive) {
                this.multiSelect(cursorPos, transPos);
                return;
            }
            this.touching = false;
            if (this.shadow) this.$emit('draggableChange', true);
            if (this.shadow && this.shadowDestination) {
                this.$emit('quickJob', {
                    vehicleKey: this.shadow.id,
                    orderList: [{ path: this.shadowDestination.id, range: -1, actionCode: 0, actionValue: 0 }]
                });
                this.shadow = undefined;
                this.shadowDestination = undefined;
                this.$store.commit(enums.DESELECT_PATHS_ALL);
            } else {
                let path = this.$root.resources.paths.find(e => {
                    return e.id === this.startPath;
                });
                if (path && path.controlPoints && path.controlPoints.length > 0 && this.shadow) {
                    let lastPoint = path.controlPoints[path.controlPoints.length - 1];
                    this.shadow.tunedPos.x = lastPoint.x;
                    this.shadow.tunedPos.y = lastPoint.y;
                    this.shadow.tunedPos.h = lastPoint.h;
                }
                this.shadow = undefined;
                this.shadowDestination = undefined;
            }
            // console.log('p-painter-vehicles.pointerUpFunc: ');
        },
        pointerLeaveFunc() {
            this.touching = false;
            if (this.shadow) this.$emit('draggableChange', true);
            // console.log('p-painter-vehicles.pointerLeaveFunc: ');
        },
        multiSelect(cp, tcp) {
            /*
             * Q: 'x' of null
             * A: pos0 or pos1 of null
             */
            if (!cp || !tcp || !this.startPoint || !this.isMultiSelectActive) return;
            let pos0 = this.startPoint,
                pos1 = tcp,
                p0 = {},
                p1 = {};
            this.startPoint = null;
            // console.warn('p-painter-vehicles.pointerUpFunc: ', pos0, pos1);

            p0.x = Math.min(pos0.x, pos1.x);
            p0.y = Math.min(pos0.y, pos1.y);
            p1.x = Math.max(pos0.x, pos1.x);
            p1.y = Math.max(pos0.y, pos1.y);
            let selects = [];
            this.$root.resources.vehicles.forEach(c => {
                if (!c.tunedPos || !Object.keys(c.tunedPos).length) return;
                let tag = c.tunedPos.x >= p0.x && c.tunedPos.x <= p1.x && c.tunedPos.y >= p0.y && c.tunedPos.y <= p1.y;
                if (tag) selects.push(c.id);
            });
            // console.warn('p-painter-paths.pointerUpFunc: ', p0, p1, selects);
            if (selects && selects.length) this.$store.commit(enums.DESELECT_VEHICLES_ALL, selects);
            this.$emit('vehiclesChange', selects);
        },

        // Cache for offscrren render. 
        calculateModelCache() {
            let models = this.$root.resources.models || [];
            if (models && this.getModelCacheLength() === models.length) return;
            // console.warn('p-painter-vehicles.calculateModelCache: ', this.getModelCacheLength(), models.length);
            this.modelCache = {};
            models.forEach(m => {
                let os = this.getGraphsByShapes(m.shape);
                this.setModelCtxCacheByGraphs(m.id, m, os); 
                // 2. Too large when canvas drawImage, incompatible for safari.
            });
            // console.log('p-painter-vehicle.calculateModelCache: ', this.modelCache);
        },
        getGraphsByShapes(shapeString) {
            let shapes = SVG.createGroupFromString(shapeString);
            let os = [];
            shapes.childNodes.forEach(c => {
                let o = Graph.getInstanceBySvg(null, this.map.scaling, c);
                if (o) os.push(o);
            });
            return os;
        },
        recalculateModelCache() {
            this.modelCache = {};
            this.calculateModelCache();
        },
        getModelCacheLength() {
            if (!this.modelCache) this.modelCache = {};
            return Object.keys(this.modelCache).length;
        },
        setModelCacheByGraphs(m, os) {
            this.setModelCache(m.id, os);
        },
        // Too large when canvas drawImage, incompatible for safari. 
        setModelCtxCacheByGraphs(key, m, os, label, labelSize, labelX, labelY) {
            // if (m.vehicleKey === 'TCVSIM' && !this.aa) {
            //     console.error(m);
            //     this.aa = true;
            // }
            let canvas = document.createElement('canvas');
            // canvas.setAttribute( 'style', 'position: absolute; left: 110px; top: 110px; z-index: 99999; border: red 1px solid;' ); //eslint-disable-line
            // document.body.appendChild(canvas);
            let scaling = m.size.x / 200,
                w = m.size.x / scaling,
                h = m.size.y / scaling;
            canvas.setAttribute('width', w);
            canvas.setAttribute('height', h);
            let ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.save();
            ctx.translate(canvas.width / 2, canvas.height / 2);
            ctx.scale(1 / scaling, 1 / scaling);

            os.forEach(g => {
                if (!g) return;
                g.draw(ctx);
            });
            // console.warn('+========> ', key, m.id);
            // if (m.type === VEHICLE_TYPES.FOLLOWER && label) {
            if (label) {
                // let num = parseInt(key.replace(/[^0-9]/gi, ''));
                // if (!num || isNaN(num)) return;
                if (!labelSize) labelSize = m.size.x / 3;
                if (!labelX) labelX = 0;
                if (!labelY) labelY = m.size.x / 3.5;
                ctx.save();
                ctx.rotate(90 * (Math.PI / 180));
                ctx.font = labelSize + 'px Arial';
                ctx.fillStyle = 'rgba(0,0,0,0.5)';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText(label, labelX, labelY);
                ctx.restore();
                // canvas.setAttribute( 'style', 'position: absolute; left: 110px; top: 110px; z-index: 99999; border: red 1px solid;' ); //eslint-disable-line
                // document.body.appendChild(canvas);
            }

            // console.log('p-painter-vehicle.setModelCacheByGraphs: ', m, os);
            this.setModelCache(key, canvas);
            ctx.restore();
        },
        setModelCache(key, canvas) {
            if (!this.modelCache) this.modelCache = {};
            this.modelCache[key] = canvas;
            return this.modelCache[key];
        },
        getModelCache(key) {
            if (!this.modelCache) this.modelCache = {};
            return this.modelCache[key];
        },
        editChanged(res) {
            this.editStatus = res;
        }
    }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<!-- <style scoped lang="scss"> -->
<style lang="scss">
.p-painter-vehicles {
    width: 100%;
    height: 100%;
}
</style>
